# Copyright (c) 2009, Ricardo Henriques
# All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#    * Redistributions of source code must retain the above copyright notice,
#      this list of conditions and the following disclaimer.
#    * Redistributions in binary form must reproduce the above copyright
#      notice, this list of conditions and the following disclaimer in the
#      documentation and/or other materials provided with the distribution.
#    * Neither the name of the author nor the names of its contributors
#      may be used to endorse or promote products derived from this software
#      without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGE.

#### Jython lib imports
import sys, os, time, threading, types, time, imp
#### Java lib imports
import java
from pawt import swing
from javax.swing.KeyStroke import getKeyStroke
from java.awt.event import KeyEvent
#### 3rd party libs imports
import jcrust, ij
#### My own imports
import ijtools, versions, error

class StdErrRedirector:
    def __init__(self, console):
        self.console = console
        
    def write(self, data):
        if data != '\n':
            self.console.printError(data)

class Py4IJ(jcrust.shell):
    menu={} # dictionary with the menu items
    actions={} # dictionary with the menu actions
    docs={}
    vars={'ij': ij,
          'ijtools': ijtools,
          'myglobals': None
          } # variables visible inside the console variable tree
    plugins={}
    
    def __init__(self):
        # Generate py4ij dynamic module
        self.vars['Py4IJ']=imp.new_module('py4ij')
        self.vars['Py4IJ'].runPlugin=self.runPlugin
        self.vars['Py4IJ'].listPlugins=self.listPlugins
        self.vars['Py4IJ'].PluginError=PluginError
        self.vars['myglobals'] = self.vars
        # Start custom jcrust
        self.currentpath=sys.prefix
        jcrust.shell.__init__(self, locals=self.vars, exitOnClose=1)
        sys.stderr=StdErrRedirector(self.jconsole)
        # Add runPlugin to vars
        #self.vars['runPlugin']=self.runPlugin
        ## Set some helper paths
        plugpath=ij.IJ.getDirectory("plugins")
        py4ijpath=os.path.join(plugpath, "Py4IJ")
        
        # Generate plugin menu
        self.__addMenu__('Plugins')
        self.__addMenu__('Edit Plugin', 'Plugins')
        self.__addMenuItem__('Set New Plugin in Menu', self.actionSetNewPlugin, 'Plugins')
        self.__addMenuSeparator__('Plugins')
        self.plugpath=os.path.join(sys.prefix, 'plugins')
        self.__searchPlugins__(self.plugpath, self.plugpath)
        
        # Generate help menu
        self.__installDoc__('About', 'about.html')
        self.__installDoc__('How to write Plugins', 'how to write plugins.html')
        self.__addMenu__('Help')
        self.__addMenu__('Tutorials', 'Help')
        self.__addMenuItem__('How to write Plugins', lambda: self.__setDoc__('How to write Plugins'), 'Tutorials')
        self.__addMenu__('API Docs', 'Help')
        apidocpath=os.path.join(sys.prefix, 'Docs', 'API','index.html')
        self.__addMenuItem__('Py4IJ', lambda: ijtools.openBrowser('file://%s' % apidocpath), 'API Docs')
        self.__addMenuItem__('ImageJ', lambda: ijtools.openBrowser('http://rsbweb.nih.gov/ij/developer/api/'), 'API Docs')
        self.__addMenuItem__('About', lambda: self.__setDoc__('About'), 'Help')
    
        # Pack with the new menus
        self.frame.pack()

        # Set About as default Doc
        self.__setDoc__('About')
        self.tabPane.setSelectedIndex(0)
        self.actionRefresh() 

    def __addMenu__(self, name, parent=None):
        """Adds a new menu to the main window
        name (string) - name of the menu
        parent (string) - name of the parent menu, if none the menu is added to the main window"""
        if parent==None: parent=self.frame.getJMenuBar()
        else: parent=self.menu[parent]['menu']
        m=swing.JMenu(name)
        parent.add(m)
        self.menu[name]={'menu': m}

    def __addMenuSeparator__(self, parent):
        """Adds a new menu separator
        parent (string) - name of the parent menu"""
        parent=self.menu[parent]['menu']
        parent.addSeparator()

    def __addMenuItem__(self, name, command, parent, enabled=1, accelerator=None):
        """Adds a new menu item
        name (string) - name for the menu item
        command (function) - command to be runned on item click
        parent (string) - name of the parent menu
        enabled (boolean) - if true the item can be clicked
        accelerator (char) - key to be used has a keyboard shortcut"""
        self.actions[name]=command
        mi=swing.JMenuItem(name)
        mi.actionPerformed=lambda e: self.__menuAction__(e, command)
        mi.setEnabled(enabled)
        if accelerator:
            mi.setAccelerator(getKeyStroke(accelerator, KeyEvent.CTRL_MASK))
        self.menu[parent][name]=mi
        self.menu[parent]['menu'].add(mi)
        
    def __menuAction__(self, event, command):
        #left click
        if event.getModifiers()==16:
            threading.Thread(target=command).start()
        #ctrl left click
        elif event.getModifiers()==18:
            command()

    def __searchPlugins__(self, plugpath, root):
        "Searches for plugins of plugin folder"
        for filename in os.listdir(plugpath):
            filepath=os.path.join(plugpath, filename)
            if os.path.isfile(filepath) and filename[0]=='$':
                self.__installPlugins__(filepath, root)
            elif os.path.isdir(filepath):
                self.__searchPlugins__(filepath, root)

    def __installPlugins__(self, plugpath, root):
        "Installs found plugins"
        plugdir, plugname = os.path.split(plugpath)
        plugtitle = plugname.replace('$', '').replace('_', ' ').replace('.py', '')
        if plugdir==root:
            self.__addMenuItem__(plugtitle, lambda: self.runPlugin(plugpath), 'Plugins')
            self.__addMenuItem__(' '+plugtitle, lambda: self.__editPlugin__(plugpath), 'Edit Plugin')
        else:
            parent=plugdir.replace(root, '')[1:]
            if self.menu.has_key(parent)==False:
                self.__addMenu__(parent, 'Plugins')
                self.__addMenu__(' '+parent, 'Edit Plugin')
            self.__addMenuItem__(plugtitle, lambda: self.runPlugin(plugpath), parent)
            self.__addMenuItem__(' '+plugtitle, lambda: self.__editPlugin__(plugpath), ' '+parent)
        self.plugins[plugtitle]=plugpath
            
    def runPlugin(self, plugpath, **extravars):
        """Runs a plugin. Returns True if plugin was run successfully or
        the plugin error if not.
        plugpath -> path for the plugin file
        extravars -> extra variables to be added to the plugin local variabes"""
        locals={'myglobals': self.vars}
        for key in extravars.keys(): locals[key]=extravars[key]
        if os.path.isfile(plugpath):
            pass
        elif os.path.isfile(os.path.join(sys.prefix, 'plugins', plugpath)):
            plugpath=os.path.join(sys.prefix, 'plugins', plugpath)
        else:
            try:
                plugpath=imp.find_module(plugpath)[1]
            except ImportError:
                raise error.PluginNotFound, plugpath
        err=self.actionRunCodeConsole(filepath=plugpath, local_namespace=locals,
                                      global_namespace=self.vars)
        self.actionRefresh()
        return err
        
    def listPlugins(self):
        "Returns a dictionary with plugin name : plugin path"
        return self.plugins

    def __editPlugin__(self, plugpath):
        "Opens plugins in the editor"
        self.filename=plugpath
        text=open(self.filename).read()
        self.editor.text=text
        self.frame.title=self.filename
        self.tabPane.setSelectedIndex(1)

    def __installDoc__(self, docname, url):
        if not "http://" in url:
            url=os.path.join(sys.prefix, 'Docs', url)
            # if windows
            if '\\' in url: url='file:///'+url.replace('\\', '/')
            # if unix
            else: url='file://'+url
        self.docs[docname]=jcrust.DocBrowser()
        self.docs[docname].open_url(url)

    def __addDoc__(self, docname, url):
        """Adds a new doc tab"""
        if not "http://" in url:
            # if windows
            if '\\' in url: url='file:///'+url.replace('\\', '/')
            # if unix
            else: url='file://'+url
        doc=jcrust.DocBrowser()
        try:
            doc.open_url(url)
            self.tabPane.addTab("Doc: "+docname, doc.get_pane())
            self.docs['pytools']=doc
            self.frame.setSize(apply(java.awt.Dimension, self.frame_size))
        except:
            pass
        
    def __setDoc__(self, docname):
        self.tabPane.remove(2)
        self.tabPane.addTab("Doc:"+docname, self.docs[docname].get_pane())
        self.tabPane.setSelectedIndex(2)
            
    def actionSetNewPlugin(self, event=None):
        fileChooser=swing.JFileChooser(self.currentpath)
        fileChooser.showOpenDialog(self.consoleFrame)
        self.currentpath=fileChooser.getCurrentDirectory().getAbsolutePath()
        filename=os.path.join(self.currentpath,fileChooser.getSelectedFile().getName())
        self.__installPlugins__(filename, self.plugpath)
        self.refresh()

    def actionCheckForUpdate(self, event=None):
        pass
        
    def refresh(self):
        self.jconsole.resetbuffer()
        self.jconsole.printPrompt()
        self.actionRefresh()
        
class PluginError(Exception):
    def __init__(self, value):
        self.value = value
    def __str__(self):
        return repr(self.value)
